package cn.edu.zzuli.nothinglink.ws;

import cn.edu.zzuli.nothinglink.constant.ResCodeConst;
import cn.edu.zzuli.nothinglink.entity.R;
import cn.edu.zzuli.nothinglink.service.ProblemService;
import cn.edu.zzuli.nothinglink.service.SignService;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;

@ServerEndpoint("/live_link/{stuId}/{stream}")
@Component
@Slf4j
public class LiveLinkEndPoint {

    //concurrent包的线程安全Set
    //key 为每个直播间的 stream
    //value 为 每个 steam 的 学生连接对象
    private static final ConcurrentHashMap<String, ConcurrentHashMap<String, LiveLinkEndPoint>>
            clients = new ConcurrentHashMap<>();

    //当前对应的 session 对象
    private Session session;

    //websocket 里面使用 @Autowired 注入 service 或 bean 时，报空指针异常
    //这是因为 每一个客户端对应一个 endPoint 对象(多对象)， 但spring @Autowired 管理的bean为单例（单对象）
    //这两个冲突了, 所以需要自动注入的时候，加一个 static 关键字就能解决这个问题。
    private static SignService signService;

    private static ProblemService problemService;

    //注意此 set 方法一定不能为 static
    @Autowired
    public void setSignService(SignService signService) {
        LiveLinkEndPoint.signService = signService;
    }

    @Autowired
    public void setProblemService(ProblemService problemService) {
        LiveLinkEndPoint.problemService = problemService;
    }

    /**
     * 在 websocket 连接建立时调用
     * @param session 注意这个session，并不是 HttpSession
     * @param stream 当前直播的流
     * @param stuId 当前连接的学生的id。
     */
    @OnOpen
    public void onOpen(Session session,
                       @PathParam(value = "stream") String stream,
                       @PathParam(value = "stuId") Integer stuId) {
        this.session = session;
        if (!clients.containsKey(stream)) {
            //没有，说明是第一次进直播间的人。此时我们创建一个新的 ConcurrentHashMap
            //加类锁，每次只有一个进来
            synchronized (LiveLinkEndPoint.class) {
                //双检查机制，防止两个线程同时进到第一次判断里。
                //这里必须加双检测。如果两个线程同时进入第一次判断，第一个线程得到锁，第二个线程等待锁。
                //第一个线程执行完之后，释放锁，第二个线程开始进入同步代码块。
                //此时如果没有该判断，会创建两个 ConcurrentHashMap对象。
                if (!clients.containsKey(stream)) {
                    //再次判断后，还是没有该对象，此时才进行 new
                    ConcurrentHashMap<String, LiveLinkEndPoint> curStreamClients = new ConcurrentHashMap<>();
                    curStreamClients.put(stuId.toString(), this);

                    clients.put(stream, curStreamClients);
                }else {
                    //否则，直接获取。
                    ConcurrentHashMap<String, LiveLinkEndPoint> curStreamClients = clients.get(stream);
                    curStreamClients.put(stuId.toString(), this);
                }

            }
        }else {
            //既然有，那就不用创建了。直接获取放入。
            ConcurrentHashMap<String, LiveLinkEndPoint> curStreamClients = clients.get(stream);
            curStreamClients.put(stuId.toString(), this);
        }
    }

    /**
     * 接收到 客户端发送数据时调用
     * @param message
     * @param session
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        try {
            R msg = JSON.parseObject(message, R.class);

            if (msg.getCode().equals(ResCodeConst.HEART_CHECK.code())) {
                session.getBasicRemote().sendText(JSON.toJSONString(
                        //202 代表当前是心跳检测的回应。
                        R.success().setCode(ResCodeConst.HEART_CHECK.code())
                                .appendMsg("pong!")
                ));
            }else if (msg.getCode().equals(ResCodeConst.SIGN.code())) {
                if (signService.saveStuSignData(msg)) {
                    session.getBasicRemote().sendText(JSON.toJSONString(
                            R.success().setCode(ResCodeConst.MESSAGE.code())
                                    .add("msg", "签到成功哦 :)")
                    ));
                }else {
                    session.getBasicRemote().sendText(JSON.toJSONString(
                            R.success().setCode(ResCodeConst.MESSAGE.code())
                                    .add("error", "签到失败，可能签到过期了 :) ")
                    ));
                }
            }else if (msg.getCode().equals(ResCodeConst.PROBLEM.code())) {
                if (problemService.saveStuProblemData(msg)) {
                    session.getBasicRemote().sendText(JSON.toJSONString(
                            R.success().setCode(ResCodeConst.MESSAGE.code())
                                    .add("msg", "问题提交成功哦 :)")
                    ));

                }else {
                    session.getBasicRemote().sendText(JSON.toJSONString(
                            R.success().setCode(ResCodeConst.MESSAGE.code())
                                    .add("error", "提交失败:) 请检查您的信息和内容")
                    ));
                }
            }else if (msg.getCode().equals(ResCodeConst.CHAT.code())) {
                //聊天内容，这里进行转发就行了。
                Integer teaId = (Integer) msg.getData().get("teaId");
                String stream = (String) msg.getData().get("stream");
                if (teaId != null && stream != null) {
                    //对学生端进行转发
                    LiveLinkEndPoint.broadCast2AllStuInTheLive(msg, stream);
                    //对教师端进行转发。
                    LiveStartEndPoint.send2UserById(teaId, msg);
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 关闭连接时调用
     * @param stuId
     */
    @OnClose
    public void onClose(@PathParam(value = "stuId")Integer stuId){
        clients.remove(stuId.toString());
    }

    /**
     * 连接异常的时候关闭
     * @param session
     * @param throwable
     */
    @OnError
    public void onError(Session session,
                        @PathParam(value = "stuId")Integer stuId,
                        Throwable throwable) {
        System.out.println(throwable);
        if (clients.containsKey(stuId.toString())) {
            clients.remove(stuId.toString());
        }
    }

    /**
     * 对所有客户端广播这个消息。
     * @param r
     */
    public static void broadCast2AllStuInTheLive(R r, String stream) {
        ConcurrentHashMap<String, LiveLinkEndPoint> curStreamClients = clients.get(stream);
        if (curStreamClients == null) return;
        curStreamClients.forEach((k, v) -> {
            try {
                if (v.session.isOpen()) {
                    v.session.getBasicRemote()
                            .sendText(JSON.toJSONString(r));
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        });

    }

}
